﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.IO;
using System.Reflection;
using Ruandao.MiniAccess.OrMap;
using System.Dynamic;


namespace Ruandao.MiniAccess.Sqlite
{
    /// <summary>
    /// 用来保存OR映射文件中的&lt;entity&gt;标签的内容。
    /// </summary>
    /// <remarks>
    /// 该类的实例用来如实的记录xml文件中&lt;entity&gt;中的属性设置，
    /// 除了必选属性外都允许null值和""值，前者表示xml中没有显式设定值(也就是使用默认值)，
    /// 后者表示显式设定了空字符串。
    /// <br/>在属性设置时检查必填属性和枚举属性的正确性，如果不合法则抛出异常。
    /// </remarks>
    public class EntityTag
    {
        private string name;

        /// <summary>
        /// 用指定的实体类型名构造实体标签对象，类型名不包括命名空间名字。
        /// <br/>异常：OrMapException(实体的类型名为空串。)
        /// </summary>
        /// <param name="name">实体的类型名。</param>
        public EntityTag(string name)
        {
                if ( name == null)
                    throw new OrMapException("mdm.xml文件中entity标签的name属性必须指定，而且不能为空串。");
                this.name = name; 
        }

        /// <summary>
        /// "name" Attribute: 实体的类型名，不包括命名空间名字。
        /// </summary>
        public string Name
        {
            get { return name; }
        }

        private string table = null;
        /// <summary>
        /// "table" Attribute: 对应的数据库表的名称。
        /// </summary>
        public string Table
        {
            get { return table; }
            set { table = value; }
        }

        private string commandType;
        /// <summary>
        /// "command-type" Attribute: 命令方式，指出是存储过程还是SQL文本。
        /// <br/>异常：OrMapException(设置时指定了无效属性)
        /// </summary>
        public string CommandType
        {
            get { return commandType; }
            set
            {
                if(value!=null)
                {
                    if (!(value == "StoredProcedure" || value == "Text"))
                        throw new OrMapException("非法属性值:" + value + ",mdm.xml文件中entity标签的command-type的取值只能是\"StoreProcedure\"或者\"Text\",区分大小写，如不指定则表示采用默认值。默认值由数据提供者确定。");
                }
                commandType = value;
            }
        }

        private string insert;
        /// <summary>
        /// "insert" Attribute: 执行插入命令的存储过程名或者SQL文本。
        /// </summary>
        public string Insert
        {
            get { return insert; }
            set { insert = value; }
        }

        private string delete;
        /// <summary>
        /// "delete" Attribute: 执行按主键删除命令的存储过程名或者SQL文本。
        /// </summary>
        public string Delete
        {
            get { return delete; }
            set { delete = value; }
        }

        private string multipleDelete;
        /// <summary>
        /// "multiple-delete" Attribute: 执行按查询条件批量删除命令的存储过程名或者SQL文本。
        /// </summary>
        public string MultipleDelete
        {
            get { return multipleDelete; }
            set {  multipleDelete = value; }
        }

        private string update;
        /// <summary>
        /// "update" Attribute: 执行按主键更新记录命令的存储过程名或者SQL文本。
        /// </summary>
        public string Update
        {
            get { return update; }
            set { update = value; }
        }

        private string fetch;
        /// <summary>
        /// "fetch" Attribute: 执行按主键查找记录命令的存储过程名或者SQL文本。
        /// </summary>
        public string Fetch
        {
            get { return fetch; }
            set { fetch = value; }
        }

        private string search;
        /// <summary>
        /// "search" Attribute: 执行按条件查找记录命令的存储过程名或者SQL文本。
        /// </summary>
        public string Search
        {
            get { return search; }
            set { search = value;  }
        }

        private IList<PropertyTag> propertyList = new List<PropertyTag>();
        /// <summary>
        /// 类型中映射到表字段的列表。
        /// <br/>ArgumentNullException(属性不允许设置为null)
        /// </summary>
        public IList<PropertyTag> PropertyList
        {
            get { return propertyList; }
            set { propertyList = value; }
        }

    }

    /// <summary>
    /// 实体的属性标签类。
    /// </summary>
    /// <remarks>可选属性允许赋null值，但是不允许赋""值。在设置属性时如果提供的值不合法则抛出OrMapException异常。</remarks>
    public class PropertyTag
    {
        /// <summary>
        /// 根据属性和映射类型构造实例。
        /// <br/>OrMapException(指定的属性名称为空串)。
        /// </summary>
        /// <param name="mapType">映射类型,对应的有效标签有&lt;primary-key&gt;,&lt;property&gt;,&lt;many-to-one&gt;,&lt;one-one&gt;。</param>
        /// <param name="name">属性名。注意：可以是私有属性。</param>
        public PropertyTag(ColumnMapType mapType, string name)
        {
            if ( string.IsNullOrEmpty(name) )
                throw new OrMapException("mdm.xml文件中实体的属性标签（所有entity的子标签）的name属性必须指定，而且不能为空串。");
            this.name = name;
            this.mapType = mapType;
        }

        private ColumnMapType mapType;
        /// <summary>
        /// 映射类型,对应的有效标签有&lt;primary-key&gt;,&lt;property&gt;,&lt;many-to-one&gt;,&lt;one-one&gt;。
        /// </summary>
        public ColumnMapType MapType
        {
            get { return mapType; }
        }

        private string name;
        /// <summary>
        /// "name" Attribute: 实体的属性名称。
        /// </summary>
        public string Name
        {
            get { return name; }
        }

        private string column;
        /// <summary>
        /// "column" Attribute: 字段名称。
        /// <br/>异常：OrMapException(设置属性值为空字符串""，注意设为null不抛出异常，表示使用默认值。)
        /// </summary>
        public string Column
        {
            get { return column; }
            set 
            {
                if (value != null && value == "")
                {
                    throw new OrMapException("column属性值非法, xml文件中不能显示指定字段名为空字符串。");
                }
                column = value; 
            }
        }

        private string columnType;
        /// <summary>
        /// "column-type" Attribute: 字段类型。
        /// <br/>异常：OrMapException(设置属性值为空字符串""，注意设为null不抛出异常，表示使用默认值。)
        /// </summary>
        public string ColumnType
        {
            get { return columnType; }
            set
            {
                if (value != null && value == "")
                {
                    throw new OrMapException("comumn-type属性值非法, xml文件中不能显式指定字段的类型为空字符串。");
                }
                columnType = value;
            }
        }

        private string update;
        /// <summary>
        /// "update" Attribute: 在更新命令中是否包括该属性的值。
        /// <br/>异常：OrMapException(指定的值不是null、"false"和"true"之一)
        /// </summary>
        public string Update
        {
            get { return update; }
            set
            {
                if (value != null)
                {
                    if( ! ( value=="true" || value=="false" ) )
                    throw new OrMapException("update属性值非法, 可选属性，显式的有效值为true或false之一。");
                }
                update = value;
            }
        }

        private string insert;
        /// <summary>
        /// “insert" Attribute: 在插入命令中是否包括该属性的值。
        /// <br/>异常：OrMapException(指定的值不是null、"false"和"true"之一)
        /// </summary>
        public string Insert
        {
            get { return insert; }
            set
            {
                if (value != null)
                {
                    if (!(value == "true" || value == "false"))
                        throw new OrMapException("insert属性值非法, 可选属性，显式的有效值为true或false之一。");
                }
                insert = value;
            }
        }
    }

    /// <summary>
    /// 映射文件类，用来读取映射文件的内容。
    /// </summary>
    public class OrMapFile
    {
        //private string connectionName;
        private string entityNamespace;
        private bool isDebug;
        private IList<EntityTag> entityTagList = new List<EntityTag>();

        /// <summary>
        /// 构造函数。构造OrMapFile所完成的功能是定位映射文件并且读出所有配置项的内容。
        /// 映射文件的文件名约定由实体所在程序集的名称来确定，格式是程序集名称（不包括.dll）
        /// 加上".mdm.xml"的扩展名。
        /// 映射文件的位置在应用程序所在目录或者程序集所在目录下，也可以在上述两个目录的
        /// minidacmap子目录下。
        /// <br/>异常：IOException(无法定位映射文件), OrMapException(mdm.xml格式错误)
        /// </summary>
        /// <param name="assembly">需要映射的实体所在的程序集，用来确定映射文件。</param>
        /// <param name="providerName">映射文件中provider的name属性值。</param>
        /// <param name="accessType"></param>
        public OrMapFile(Assembly assembly, string accessType, string providerName)
        {
            string ormFile = LocateMdmFile(assembly);

            XmlDocument dom = new XmlDocument();
            dom.Load(ormFile);
            dynamic xml = dom;
            var x = xml.DocumentElement;
            var b = x.Name;
            if (xml.DocumentElement.Name != "miniaccess-mapping") {
                throw new OrMapException(
                        string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：文件的根元素必须是<miniaccess-mapping>",
                        assembly.GetName().Name, ormFile)
                    );
            }

            // 定位到当前设定的provider所在的元素。
            dynamic typeNode = null;
            foreach (dynamic node in dom.DocumentElement.ChildNodes) {
                if (node.Name != "access-type") continue;

                if ( ((XmlAttributeCollection) node.Attributes)["name"].Value == accessType) {
                    typeNode = node;
                    break;
                }

            }

            if (typeNode == null) {
                throw new OrMapException(
                        string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：根元素下找不到指定access-type名称的配置元素<access-type name=\"{2}\">",
                        assembly.GetName().Name, ormFile, accessType)
                );
            }

            // 定位到当前设定的provider所在的元素。
            dynamic providerNode = null;
            foreach (dynamic node in typeNode.ChildNodes) {
                if (node.Name != "provider") continue;

                if ( ((XmlAttributeCollection)node.Attributes)["name"].Value == providerName) {
                    entityNamespace = ((XmlAttributeCollection)node.Attributes)["entity-namespace"].Value;
                    if (string.IsNullOrEmpty(entityNamespace))
                        throw new OrMapException(
                                string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：<provider name=\"{2}\">元素的entity-namespace不能是null或者empty",
                                assembly.GetName().Name, ormFile, providerName)
                        );

                    isDebug = true;
                    if (((XmlAttributeCollection)node.Attributes)["is-debug"] != null) {
                        if ( ((XmlAttributeCollection)node.Attributes)["is-debug"].Value.ToLower() == "false") isDebug = false;
                    }

                    providerNode = node;
                    break;
                }

            }

            if (providerNode == null) {
                throw new OrMapException(
                        string.Format("程序集{0}.dll对应的映射文件({1})格式不正确：找不到指定provider名称的配置元素<provider name=\"{2}\">",
                        assembly.GetName().Name, ormFile, providerName)
                );
            }

            ReadOrMapSettings(providerNode);
        }

        /// <summary>
        /// 如果IsDebug为真，则每次运行时重新生成数据库中的存储过程。
        /// </summary>
        public bool IsDebug
        {
            get { return isDebug; }
        }

        /// <summary>
        /// 实体类型所在的命名空间。
        /// </summary>
        public string EntityNamespace
        {
            get { return entityNamespace; }
        }

        /// <summary>
        /// 获取映射文件中所有的实体列表。
        /// </summary>
        public IList<EntityTag> EntityTagList
        {
            get { return entityTagList; }
        }

        /// <summary>
        /// 定位Or映射文件的位置。
        /// <br/>异常：OrMapException(没有找到文件)
        /// </summary>
        /// <param name="assembly">实体所在的程序集。</param>
        /// <returns>映射文件的绝对路径。</returns>
        private string LocateMdmFile(Assembly assembly)
        {
            string mdmfile = assembly.GetName().Name + ".mdm.xml";

            string path;
            string pathfile = "";

            path = Path.GetDirectoryName(assembly.Location);
            pathfile = Path.Combine(path, mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            path = AppDomain.CurrentDomain.BaseDirectory;

            pathfile = Path.Combine(path, mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            pathfile = Path.Combine(path, "Bin\\" + mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            pathfile = Path.Combine(path, "App_Data\\" + mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            pathfile = Path.Combine(path, "minidacmap\\" + mdmfile);
            if (File.Exists(pathfile))
            {
                return pathfile;
            }

            throw new OrMapException("无法定位" + mdmfile + "文件，其位置约定是应用程序域的主目录或者主目录下的名为Bin、App_Data或minidacmap的子目录，或者是和实体程序集位于同一个目录下。");
        }

        private void ReadOrMapSettings(XmlNode providerNode)
        {

            entityTagList = new List<EntityTag>();
            // 遍历所有的实体类型
            foreach (XmlNode node in providerNode.ChildNodes)
            {
                //检测node是class, 如果不是则抛出错误。
                if (node.LocalName != "entity")
                {
                    throw new OrMapException("映射文件格式不正确：文件的<provider>元素下必须是<entity>元素。");
                }
                EntityTag entity = ReadTableMapFromXml(node);

                entity.PropertyList = new List<PropertyTag>();

                // 遍历所有的属性、主键和外建字段。
                foreach (XmlNode subnode in node.ChildNodes)
                {
                    PropertyTag property = ReadColumnMapFromXml(subnode);
                    entity.PropertyList.Add(property);
                }
                entityTagList.Add(entity);
            }
        }

        // 注意node.Attributes["..."]为null表示xml文件中没有指定该属性，因而程序
        // 程序可以把它解释为使用系统的默认值，而node.Attributes["..."]为""则表示
        // xml文件中显示指定该属性为空字符串。
        private EntityTag ReadTableMapFromXml(XmlNode node)
        {
            EntityTag entity = new EntityTag(node.Attributes["name"].Value);
            if (node.Attributes["table"] != null)
            {
                entity.Table = node.Attributes["table"].Value;
            }
            if (node.Attributes["command-type"] != null)
            {
                entity.CommandType = node.Attributes["command-type"].Value;
            }
            if (node.Attributes["insert"] != null)
            {
                entity.Insert = node.Attributes["insert"].Value;
            }
            if (node.Attributes["delete"] != null)
            {
                entity.Delete = node.Attributes["delete"].Value;
            }
            if (node.Attributes["multiple-delete"] != null)
            {
                entity.MultipleDelete = node.Attributes["multiple-delete"].Value;
            }
            if (node.Attributes["update"] != null)
            {
                entity.Update = node.Attributes["update"].Value;
            }
            if (node.Attributes["fetch"] != null)
            {
                entity.Fetch = node.Attributes["fetch"].Value;
            }
            if (node.Attributes["search"] != null)
            {
                entity.Search = node.Attributes["search"].Value;
            }
            return entity;
        }

        // 注意node.Attributes["..."]为null表示xml文件中没有指定该属性，因而程序
        // 程序可以把它解释为使用系统的默认值，而node.Attributes["..."]为""则表示
        // xml文件中显示指定该属性为空字符串。
        private PropertyTag ReadColumnMapFromXml(XmlNode node)
        {
            ColumnMapType type = ColumnMapTypeFromString(node.LocalName);

            PropertyTag property = new PropertyTag(type, node.Attributes["name"].Value );

            if (node.Attributes["column"] != null)
            {
                property.Column = node.Attributes["column"].Value;
            }
            if (node.Attributes["column-type"] != null)
            {
                property.ColumnType = node.Attributes["column-type"].Value;
            }
            if (node.Attributes["update"] != null)
            {
                property.Update = node.Attributes["update"].Value;
            }
            if (node.Attributes["insert"] != null)
            {
                property.Insert = node.Attributes["insert"].Value;
            }
            return property;
        }

        private ColumnMapType ColumnMapTypeFromString(string name)
        {
            name = name.ToLower();
            switch (name)
            {
                case "primary-key":
                    return ColumnMapType.PrimaryKeyMap;
                case "one-to-one":
                    return ColumnMapType.OneToOneMap;
                case "many-to-one":
                    return ColumnMapType.ManyToOneMap;
                case "property":
                    return ColumnMapType.PropertyMap;
                default:
                    throw new OrMapException("不支持的字段映射类型：" + name);
            }
        }
    }
}
